/* RTCP code taken directly from the most recent RTP specification:
 *     RFC 3550
 * Implementation
 */

#include "rtcp_from_spec.h"

/*****

A.7 Computing the RTCP Transmission Interval

   The following functions implement the RTCP transmission and reception
   rules described in Section 6.2. These rules are coded in several
   functions:

       o  rtcp_interval() computes the deterministic calculated
          interval, measured in seconds.  The parameters are defined in
          Section 6.3.

       o  OnExpire() is called when the RTCP transmission timer expires.

       o  OnReceive() is called whenever an RTCP packet is received.

   Both OnExpire() and OnReceive() have event e as an argument. This is
   the next scheduled event for that participant, either an RTCP report
   or a BYE packet.  It is assumed that the following functions are
   available:

       o  Schedule(time t, event e) schedules an event e to occur at
          time t. When time t arrives, the function OnExpire is called
          with e as an argument.

       o  Reschedule(time t, event e) reschedules a previously scheduled
          event e for time t.

       o  SendRTCPReport(event e) sends an RTCP report.

       o  SendBYEPacket(event e) sends a BYE packet.

       o  TypeOfEvent(event e) returns EVENT_BYE if the event being
          processed is for a BYE packet to be sent, else it returns
          EVENT_REPORT.

       o  PacketType(p) returns PACKET_RTCP_REPORT if packet p is an
          RTCP report (not BYE), PACKET_BYE if its a BYE RTCP packet,
          and PACKET_RTP if its a regular RTP data packet.

       o  ReceivedPacketSize() and SentPacketSize() return the size of
          the referenced packet in octets.

       o  NewMember(p) returns a 1 if the participant who sent packet p
          is not currently in the member list, 0 otherwise. Note this
          function is not sufficient for a complete implementation
          because each CSRC identifier in an RTP packet and each SSRC in
          a BYE packet should be processed.

       o  NewSender(p) returns a 1 if the participant who sent packet p
          is not currently in the sender sublist of the member list, 0
          otherwise.

       o  AddMember() and RemoveMember() to add and remove participants
          from the member list.

       o  AddSender() and RemoveSender() to add and remove participants
          from the sender sublist of the member list.
*****/


double rtcp_interval(int members,
                     int senders,
                     double rtcp_bw,
                     int we_sent,
                     double avg_rtcp_size,
                     int initial) {
    /*
     * Minimum average time between RTCP packets from this site (in
     * seconds).  This time prevents the reports from `clumping' when
     * sessions are small and the law of large numbers isn't helping
     * to smooth out the traffic.  It also keeps the report interval
     * from becoming ridiculously small during transient outages like
     * a network partition.
     */
    double const RTCP_MIN_TIME = 5.;
    /*
     * Fraction of the RTCP bandwidth to be shared among active
     * senders.  (This fraction was chosen so that in a typical
     * session with one or two active senders, the computed report
     * time would be roughly equal to the minimum report time so that
     * we don't unnecessarily slow down receiver reports.) The
     * receiver fraction must be 1 - the sender fraction.
     */
    double const RTCP_SENDER_BW_FRACTION = 0.25;
    double const RTCP_RCVR_BW_FRACTION = (1 - RTCP_SENDER_BW_FRACTION);
    /*
     * To compensate for "unconditional reconsideration" converging to a
     * value below the intended average.
     */
    double const COMPENSATION = 2.71828 - 1.5;

    double t;                   /* interval */
    double rtcp_min_time = RTCP_MIN_TIME;
    int n;                      /* no. of members for computation */

    /*
     * Very first call at application start-up uses half the min
     * delay for quicker notification while still allowing some time
     * before reporting for randomization and to learn about other
     * sources so the report interval will converge to the correct
     * interval more quickly.
     */
    if (initial) {
        rtcp_min_time /= 2;
    }

    /*
     * If there were active senders, give them at least a minimum
     * share of the RTCP bandwidth.  Otherwise all participants share
     * the RTCP bandwidth equally.
     */
    n = members;
    if (senders > 0 && senders < members * RTCP_SENDER_BW_FRACTION) {
        if (we_sent) {
            rtcp_bw *= RTCP_SENDER_BW_FRACTION;
            n = senders;
        } else {
            rtcp_bw *= RTCP_RCVR_BW_FRACTION;
            n -= senders;
        }
    }

    /*
     * The effective number of sites times the average packet size is
     * the total number of octets sent when each site sends a report.
     * Dividing this by the effective bandwidth gives the time
     * interval over which those packets must be sent in order to
     * meet the bandwidth target, with a minimum enforced.  In that
     * time interval we send one report so this time is also our
     * average time between reports.
     */
    t = avg_rtcp_size * n / rtcp_bw;
    if (t < rtcp_min_time) t = rtcp_min_time;

    /*
     * To avoid traffic bursts from unintended synchronization with
     * other sites, we then pick our actual next report interval as a
     * random number uniformly distributed between 0.5*t and 1.5*t.
     */
    t = t * (drand48() + 0.5);
    t = t / COMPENSATION;
    return t;
}

void OnExpire(event e,
              int members,
              int senders,
              double rtcp_bw,
              int we_sent,
              double *avg_rtcp_size,
              int *initial,
              time_tp tc,
              time_tp *tp,
              int *pmembers) {
    /* This function is responsible for deciding whether to send
     * an RTCP report or BYE packet now, or to reschedule transmission.
     * It is also responsible for updating the pmembers, initial, tp,
     * and avg_rtcp_size state variables. This function should be called
     * upon expiration of the event timer used by Schedule(). */

    double t;     /* Interval */
    double tn;    /* Next transmit time */

    /* In the case of a BYE, we use "unconditional reconsideration" to
     * reschedule the transmission of the BYE if necessary */

    if (TypeOfEvent(e) == EVENT_BYE) {
        t = rtcp_interval(members,
                          senders,
                          rtcp_bw,
                          we_sent,
                          *avg_rtcp_size,
                          *initial);
        tn = *tp + t;
        if (tn <= tc) {
            SendBYEPacket(e);
            exit(1);
        } else {
            Schedule(tn, e);
        }

    } else if (TypeOfEvent(e) == EVENT_REPORT) {
        t = rtcp_interval(members,
                          senders,
                          rtcp_bw,
                          we_sent,
                          *avg_rtcp_size,
                          *initial);
        tn = *tp + t;

        if (tn <= tc) {
            SendRTCPReport(e);
            *avg_rtcp_size = (1. / 16.) * SentPacketSize(e) +
                             (15. / 16.) * (*avg_rtcp_size);
            *tp = tc;

            /* We must redraw the interval. Don't reuse the
               one computed above, since its not actually
               distributed the same, as we are conditioned
               on it being small enough to cause a packet to
               be sent */

            t = rtcp_interval(members,
                              senders,
                              rtcp_bw,
                              we_sent,
                              *avg_rtcp_size,
                              *initial);

            Schedule(t + tc, e);
            *initial = 0;
        } else {
            Schedule(tn, e);
        }
        *pmembers = members;
    }
}


void OnReceive(packet p,
               event e,
               int *members,
               int *pmembers,
               int *senders,
               double *avg_rtcp_size,
               double *tp,
               double tc,
               double tn) {
    /* What we do depends on whether we have left the group, and
     * are waiting to send a BYE (TypeOfEvent(e) == EVENT_BYE) or
     * an RTCP report. p represents the packet that was just received. */

    if (PacketType(p) == PACKET_RTCP_REPORT) {
        if (NewMember(p) && (TypeOfEvent(e) == EVENT_REPORT)) {
            AddMember(p);
            *members += 1;
        }
        *avg_rtcp_size = (1. / 16.) * ReceivedPacketSize(p) +
                         (15. / 16.) * (*avg_rtcp_size);
    } else if (PacketType(p) == PACKET_RTP) {
        if (NewMember(p) && (TypeOfEvent(e) == EVENT_REPORT)) {
            AddMember(p);
            *members += 1;
        }
        if (NewSender(p) && (TypeOfEvent(e) == EVENT_REPORT)) {
            AddSender(p);
            *senders += 1;
        }
    } else if (PacketType(p) == PACKET_BYE) {
        *avg_rtcp_size = (1. / 16.) * ReceivedPacketSize(p) +
                         (15. / 16.) * (*avg_rtcp_size);

        if (TypeOfEvent(e) == EVENT_REPORT) {
            if (NewSender(p) == FALSE) {
                RemoveSender(p);
                *senders -= 1;
            }

            if (NewMember(p) == FALSE) {
                RemoveMember(p);
                *members -= 1;
            }

            if (*members < *pmembers) {
                tn = tc + (((double) *members) / (*pmembers)) * (tn - tc);
                *tp = tc - (((double) *members) / (*pmembers)) * (tc - *tp);

                /* Reschedule the next report for time tn */

                Reschedule(tn, e);
                *pmembers = *members;
            }

        } else if (TypeOfEvent(e) == EVENT_BYE) {
            *members += 1;
        }
    }
}
